
#region Imported Namespaces
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;
#endregion

namespace PHSRAG.WebControls
{
	#region Class Calendar
	/// <summary>
	/// The Calendar control provides a uniform mechanism to display a month-based calendar for date selection.
	/// All interaction with such a calendar is client-based, via DHTML.
	/// </summary>
	/// <remarks>
	/// <p>The control essentially emits two elements into the browser; a label to display the selected date, and an icon
	/// to click upon to display the calendar. Unless a date has been selected, the label will say "-- Select --". This
	/// string is configurable via the Selection property.</p>
	/// <p>If a date value is set using the Selection property, ensure that the date value is of the format mm/dd/yyyy.</p>
	/// <p>More than one instance of the Calendar control may be used on a single page.</p>
	/// </remarks>
	/// <example>
	/// <p>The following example shows how the page would use this control. The registration of the control can be done
	/// simply by dropping the control on the page in design mode.</p>
	/// <p>The example shows two calendars on a single page: one uses the drop-down-list style of navigation, sets the
	/// prompt to a specific date, and specifies the begining and ending years, and the other uses default settings. </p>
	/// <code>
	/// <![CDATA[
	/// <%@ Register TagPrefix="cc1" Namespace="PHSRAG.WebControls" Assembly="PHSRAG.WebControls" %>
	/// ...
	/// <body>
	/// ...
	///		<table>
	///			<tr>
	///				<td align="right">Start Date:&nbsp;</td>
	///				<td align="left" valign="top"><cc1:Calendar id="calendar1" runat="server" DropDownListNavigation="true"
	///					BeginYear="1985" EndYear="2010"></cc1:Calendar></td>
	///				<td>&nbsp;</td>
	///				<td align="right">End Date:&nbsp;</td>
	///				<td align="left" valign="top"><cc1:Calendar id="calendar2" runat="server" DropDownListNavigation="false"
	///					</cc1:Calendar></td>
	///			</tr>
	///		</table>
	/// ...
	/// </body>
	/// ]]>
	/// </code>
	/// </example>
	[ToolboxData("<{0}:Calendar runat=server />"), Designer(typeof(CalendarDesigner))]
	public class Calendar : System.Web.UI.WebControls.WebControl, INamingContainer
	{
		#region Constants
		private const string BeginYearKey = "beginYear_3F50D491-5A56-4346-AE51-03404002A82F";
		private const string CalendarLinkAlternateText = "Calendar";
		private const string ClientScriptsKey = "clientScripts_3F50D491-5A56-4346-AE51-03404002A82F";
		private const string StartupScriptsKey = "startupScripts_3F50D491-5A56-4346-AE51-03404002A82F";
        // any client that wishes to have a Javascript function named "ValidateDateRange()" called in the "onblur"
        // event for this Calendar's TextBox must register a script with this key
        private const string ValidateDateRangeScriptKey = "validateDateRangeScript_601AE4D7-2CBB-413c-82AF-789A94B00BBF";
		private const string DropDownListNavigationKey = "dropDownListNavigation_3F50D491-5A56-4346-AE51-03404002A82F";
		private const string EndYearKey = "endYear_3F50D491-5A56-4346-AE51-03404002A82F";
		private const string CssClassKey = "cssClass_3F50D491-5A56-4346-AE51-03404002A82F";
		private const string HiddenFieldPrefix = "hidden";
		private const string PromptKey = "prompt_3F50D491-5A56-4346-AE51-03404002A82F";
		private const int	 DateTextBoxMaxLength = 10; // MM/DD/YYYY
		#endregion

		#region Instance Variables
		private ImageButton	calendarLink;	// Clicking this opens up the calendar.
		private bool		reset;
		private	TextBox		selectedDate;  // This is where the selected date will be displayed.
        private bool usePromptToSetSelection;
		#endregion

		#region Public Properties
		/// <summary>
		/// Get/Set the begining year when using the drop-down-list navigation. Default is currentYear - 10.
		/// </summary>
		public int BeginYear
		{
			get { object o = ViewState[BeginYearKey]; return (o == null) ? DateTime.Now.Year - 10 : (int)o; }
			set { ViewState[BeginYearKey] = value; }
		}

		/// <summary>
		/// Get the string representation of the Calendar control at design time. This property is typically used by
		/// controls which embed the Calendar control in them.
		/// </summary>
		public static string DesignTimeHTML
		{
			get
			{
				return string.Format("<table><tr><td align=\"right\"></td><td>&nbsp;</td><td align=\"left\">{0}</td></tr></table>",
					CalendarLinkAlternateText);
			}
		}

		/// <summary>
		/// Get/Set whether the month/year navigation is done through drop-down-lists or not. The default is to
		/// use the drop-down-list.
		/// </summary>
		public bool DropDownListNavigation
		{
			get { object o = ViewState[DropDownListNavigationKey]; return (o == null) ? true : (bool)o; }
			set { ViewState[DropDownListNavigationKey] = value; }
		}

		/// <summary>
		/// Get/Set the ending year when using the drop-down-list navigation. Default is current year + 10.
		/// </summary>
		public int EndYear
		{
			get { object o = ViewState[EndYearKey]; return (o == null) ? DateTime.Now.Year + 10 : (int)o; }
			set { ViewState[EndYearKey] = value; }
		}

		/// <summary>
		/// Get/Set the user entry (in the text box)
		/// </summary>
		public string Prompt
		{
			get
			{
				object o = ViewState[PromptKey];
                string prompt = o as string;
                return (prompt == null) ? string.Empty : (prompt == Helper.SelectPrompt ? string.Empty : prompt);
			}
			set
			{
				ViewState[PromptKey] = value; 
			}
		}

		/// <summary>
		/// Get the selected date (as a string). Since selecting a date does not result in a postback, the selection is
		/// maintained in a hidden field within the form. It is that hidden field which will be accessed to retrieve the
		/// selection. If no selection has been made, an empty string will be returned.
		/// </summary>
		public string Selection
		{
			get	
			{ 
				string date = reset ? null : Page.Request.Form[selectedDate.UniqueID];
                if (reset)
                {
                    selectedDate.Text = Prompt = string.Empty;
                    reset = !(usePromptToSetSelection = true);
                }
				return String.IsNullOrEmpty(date) ? Prompt : date;
			}
		}

        /// <summary>
        /// Get the Client ID of the TextBox contained within this Calendar control.
        /// </summary>
        public string SelectedDateClientId
        {
            get { return (selectedDate != null) ? selectedDate.ClientID : ""; }
        }
		#endregion

		#region Public Methods
		/// <summary>
		/// Reset the state of the calendar. Since the calendar maintains most of its state either in the view state or
		/// in hidden fields of the form, we can't directly reset either of the two. Instead, we simply register the fact
		/// that a reset has been requested, and do the actual reset at the time of registering client-side scripts (done
		/// during the pre-render stage).
		/// </summary>
		public void Reset()
		{
			reset = true;
		}
		#endregion

		#region Overrides
		/// <summary>
		/// Create all controls that will be on the Calendar. Notice that the creation is more than just instantiating
		/// the controls. It also involves in generating the underlying HTML so that the controls shows up per the need.
		/// </summary>
		protected override void CreateChildControls()
		{
			CreateControls();
			GenerateControlTree();
		}

		/// <summary>
		/// Propagate the style change to the WebControl and persist it to viewstate
		/// for use in OnPreRender to set the style for controls local to this class.
		/// </summary>
		public override string CssClass
		{
			get { return base.CssClass;	}
			set	{ ViewState[CssClassKey] = base.CssClass = value; }
		}

		/// <summary>
		/// Ensure that the child controls are created and initialized, and register client-side scripts.
		/// </summary>
		/// <remarks>
		/// A hidden field is registered to keep track of the current selection. This is so that the client-side javascript
		/// which manages the calendar can store the selection in that hidden field, which in turn can be retrieved on
		/// postback.
		/// </remarks>
		/// <param name="e">Argument associated with the load event</param>
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			EnsureChildControls();
		}

		/// <summary>
		/// Register client-side scripts.
		/// </summary>
		/// <remarks>
		/// <p>A hidden field is registered to keep track of the current selection. This is so that the client-side javascript
		/// which manages the calendar can store the selection in that hidden field when a calendar pop-up
		/// is used to enter the date into the textbox, which in turn can be retrieved on
		/// postback.</p>
		/// <p>If a reset has been requested (via the Reset() method), the emitted script will ensure that the hidden
		/// filed which maintains the selection is reset to an empty string.</p>
		/// </remarks>
		/// <param name="e">Argument associated with the load event</param>
		protected override void OnPreRender(EventArgs e)
		{
			if (ViewState[CssClassKey] != null)
				selectedDate.CssClass = (string)ViewState[CssClassKey];

			string selection = Selection;
			if (reset)
				selectedDate.Text = selection = string.Empty;
			else if (usePromptToSetSelection || (String.IsNullOrEmpty(selection)))
				selection = Prompt;
			reset = usePromptToSetSelection = false;

			RegisterClientScripts(selection);
			Helper.RegisterHiddenFieldWithId(Page, GetHiddenFieldID(selectedDate), selection);

            // Caution: The following code to associate a client-side javascript for click handling on the calendar link
            // MUST NOT be invoked UNTIL the calendar link control has been added to the Controls collection. This ensures
            // that the client ID of the link is unique.

            if (!IsDesignMode) 
            {
                calendarLink.Attributes["onclick"] = string.Format(
                    "calendar.AllowSelection('{0}','{1}','{2}',{3},{4},{5});return false;",
                    selectedDate.ClientID, GetHiddenFieldID(selectedDate), calendarLink.ClientID,
                    DropDownListNavigation ? "true" : "false", BeginYear, EndYear);

                if (Page.ClientScript.IsClientScriptBlockRegistered(ValidateDateRangeScriptKey))
                    selectedDate.Attributes["onblur"] = "ValidateDateInput(this);if ( !ValidateDateRange() ) {this.value=''; this.focus();}";
                else
                    selectedDate.Attributes["onblur"] = "ValidateDateInput(this);return false;";

                if (!Page.ClientScript.IsStartupScriptRegistered(StartupScriptsKey))
                {
                    Page.ClientScript.RegisterStartupScript(GetType(), StartupScriptsKey, "<script language=\"javascript\">\n" +
                        "function ValidateDateInput(field)\n" +
                        "{\n" +
                        "var dateExpression = new RegExp(\"[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}\" ,\"g\");\n" +
                        "if (field.value != '' && !field.value.match(dateExpression))\n" +
                        "{\n" +
                        "alert('Date has to be in MM/DD/YYYY format');\n field.focus();\n" +
                        "field.value='';\n" +
                        "}\n" +
                        "}\n</script>");
                }
            }

			base.OnPreRender(e);
		}

		#endregion

		#region Private Properties
		/// <summary>
		/// Determine if the control is in design mode. It is if there is not HTTP context.
		/// </summary>
		private static bool IsDesignMode
		{
			get { return HttpContext.Current == null; }
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Create all controls in the Header.
		/// </summary>
		private void CreateControls()
		{
			Controls.Clear();

			selectedDate = new TextBox();
			selectedDate.ID = "selectedDate";
			selectedDate.CssClass = (String.IsNullOrEmpty(CssClass)) ? "DataInput" : CssClass;
			selectedDate.MaxLength = DateTextBoxMaxLength;
			selectedDate.Columns = DateTextBoxMaxLength;
			selectedDate.ToolTip = "Enter date in MM/DD/YYYY format.";

			calendarLink = new ImageButton();
			calendarLink.EnableViewState = false;
			calendarLink.ID = "calendarLink";
			if (!IsDesignMode)
                calendarLink.ImageUrl = VirtualPathUtility.ToAbsolute("~/Images/Calendar.gif");
			calendarLink.AlternateText = CalendarLinkAlternateText;
		}

		/// <summary>
		/// Setup the control tree to reflect the actual (and exact) layout of the controls within the Header.
		/// </summary>
		private void GenerateControlTree()
		{
			Table table = new Table();
			Controls.Add(table);
			table.CssClass = "Calendar";

			TableRow row = new TableRow();
			table.Rows.Add(row);

			TableCell cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(selectedDate);
			cell.Controls.Add(new LiteralControl("&nbsp;"));
			cell.Controls.Add(calendarLink);
		}

		/// <summary>
		/// Generate the ID for a hidden field (client-side) based on the ID of an existing control.
		/// </summary>
		/// <param name="c">Control to base the hidden field ID on</param>
		/// <returns>Hidden field ID</returns>
		private static string GetHiddenFieldID(Control c)
		{
			return string.Format("{0}{1}", HiddenFieldPrefix, c.ClientID);
		}

		/// <summary>
		/// Register client-side javascripts to instantiate the calender object and initialize it to show the navigation
		/// either using drop-down-lists or arrows. Register a startup script to store the current value of the user entry
		/// (if it looks like a date) in the hidden field for the calendar.
		/// </summary>
		/// <param name="selection">Current selection</param>
		private void RegisterClientScripts(string selection)
		{
            if (!Page.ClientScript.IsClientScriptBlockRegistered(ClientScriptsKey))
                Page.ClientScript.RegisterClientScriptBlock(GetType(), ClientScriptsKey, string.Format(
                      "<script language=\"javascript\" src=\"{0}\"></script>\n" +
                      "<script language=\"javascript\">\nwindow.document.write(GetCalendarStyles());\n" +
                      "var calendar = new Calendar();\n" +
                      "calendar.promptText = '{1}';\n" +
                      "</script>\n", VirtualPathUtility.ToAbsolute("~/Scripts/Calendar.js"), string.Empty));


			Regex regex = new Regex(@"\d{1,2}/\d{1,2}/\d{4}", RegexOptions.Compiled);
            Page.ClientScript.RegisterStartupScript(GetType(), string.Format("{0}setSelectedDate", ClientID), string.Format(
				"<script language=\"javascript\">\n" +
				"window.document.getElementById('{0}').value='{1}';\n" +
				"</script>", selectedDate.ClientID,
				regex.Match(selection).Success ? selection : string.Empty));
		}
		#endregion

		#region Internal Methods
		/// <summary>
		/// This method is ONLY invoked by the associated designer to show the control contents at design time.
		/// </summary>
		internal void Design()
		{
			EnsureChildControls();
		}
		#endregion
	}
	#endregion

	#region Class CalendarDesigner
	/// <summary>
	/// Designer associated with the Calendar control.
	/// </summary>
	/// <remarks>
	/// This class cannot be used directly.
	/// </remarks>
	public class CalendarDesigner : ControlDesigner
	{
		#region Overrides
		/// <summary>
		/// Force the control to create its child controls for display at design time.
		/// </summary>
		/// <returns>HTML representation of the Calendar control</returns>
		public override string GetDesignTimeHtml()
		{
			((Calendar)Component).Design();
			return base.GetDesignTimeHtml();
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
